////////////////////////////////////////////////////////////////////////////////
// material.cpp
// Author       : Francesco Giordana
// Sponsored by : Anygma N.V. (http://www.nazooka.com)
// Start Date   : January 13, 2005
// Copyright    : (C) 2006 by Francesco Giordana
// Email        : fra.giordana@tiscali.it
////////////////////////////////////////////////////////////////////////////////

/*********************************************************************************
*                                                                                *
*   This program is free software; you can redistribute it and/or modify         *
*   it under the terms of the GNU Lesser General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or            *
*   (at your option) any later version.                                          *
*                                                                                *
**********************************************************************************/

#include "material.h"
#include <maya/MObject.h>
#include <maya/MObjectArray.h>
#include <maya/MIntArray.h>
#include <maya/MPlugArray.h>
#include <maya/MFnMesh.h>
#include <maya/MFnLambertShader.h>
#include <maya/MFnPhongShader.h>
#include <maya/MFnBlinnShader.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MPlug.h>

//#define DO_TEXTURES 1

// Constructor
Material::Material()
{
	clear();
}


// Destructor
Material::~Material()
{
  //m_image.release();
}


// Get material name
MString& Material::name()
{
	return m_name;
}


// Clear data
void Material::clear()
{
	m_name = "";
	m_type = MT_LAMBERT;
	m_lightingOff = false;
	m_isTextured = false;
	m_isMultiTextured = false;
	m_ambient = MColor(0,0,0,0);
	m_diffuse = MColor(0,0,0,0);
	m_specular = MColor(0,0,0,0);
	m_emissive = MColor(0,0,0,0); 
  m_refractiveIndex = 1.0f;
  m_transparency = MColor(0,0,0);
  m_diffuseCoeff = 1.0f;
  m_reflectivity = 0.5f;
	m_textures.clear();
	m_textureObjs.clear();
	m_uvsets.clear();
  m_image = NULL;
}

void Material::release_image()
{
  if(NULL != m_image)
  {
    delete m_image;
    m_image = NULL;
  }
}

// Load material data
MStatus Material::load(MFnMesh& mesh/*,MStringArray& uvsets,ParamList& params*/)
{
  //if(!shaderObj.hasFn(MFn::kDependencyNode))
    //{return MStatus::kFailure;}
  MStatus stat;
  MObjectArray shader_array;
  MIntArray face_indices;
  if(!mesh.getConnectedShaders(0, shader_array, face_indices)) 
  { return MStatus::kFailure; }

  unsigned int shader_arr_length = shader_array.length();
  for(unsigned int s=0; s<shader_arr_length; ++s)
  {
    MFnDependencyNode fn(shader_array[s]);
    MPlug shader = fn.findPlug("surfaceShader");
    MPlugArray materials;
    if(shader.connectedTo(materials, true, false))
    {
      for(unsigned int m=0; m<materials.length(); ++m)
      {
        MPlugArray plugs;
        if(materials[m].node().hasFn(MFn::kPhong))
        {
          stat = loadPhong(materials[m].node());   
        }
        else if(materials[m].node().hasFn(MFn::kBlinn))
        {
          stat = loadBlinn(materials[m].node());
        }
        else if(materials[m].node().hasFn(MFn::kLambert))
        {
          stat = loadLambert(materials[m].node());
        }
        else
        {
          stat = loadSurfaceShader(materials[m].node());
        }
      }
    }
  }

  //MFnDependencyNode shader(shaderObj);
	//MStatus stat;
	//clear();
	//read material name, adding the requested prefix
	//MString tmpStr = params.matPrefix;
	//if (tmpStr != "")
	//	tmpStr += "/";
	//tmpStr += shader.`name();
	//MStringArray tmpStrArray;
	//tmpStr.split(':',tmpStrArray);
	//m_name = "";
	//for (int i=0; i<tmpStrArray.length(); i++)
	//{
	//	m_name += tmpStrArray[i];
	//	if (i < tmpStrArray.length()-1)
	//		m_name += "_";
	//}
  //m_name = shader.name();

	//check if we want to export with lighting off option
	//m_lightingOff = params.lightingOff;

	//// GET MATERIAL DATA

	//// Check material type
	//if (shader.object().hasFn(MFn::kPhong))
	//{
	//	stat = loadPhong(shaderObj);
	//}
	//else if (shader.object().hasFn(MFn::kBlinn))
	//{
	//	stat = loadBlinn(shaderObj);
	//}
	//else if (shader.object().hasFn(MFn::kLambert))
	//{
	//	stat = loadLambert(shaderObj);
	//}
	////else if (shader.object().hasFn(MFn::kPluginHwShaderNode))
	////{
	////	stat = loadCgFxShader(pShader);
	////}
	//else
	//{
	//	stat = loadSurfaceShader(shaderObj);
	//}

	// Get textures data
#ifdef DO_TEXTURES
	MPlugArray colorSrcPlugs;
	MPlugArray texSrcPlugs;
	MPlugArray placetexSrcPlugs;
	if (m_isTextured)
	{
		// Translate multiple textures if material is multitextured
		if (m_isMultiTextured)
		{
			// Get layered texture node
			MFnDependencyNode* pLayeredTexNode = NULL;
			if (m_type == MT_SURFACE_SHADER)
				shader.findPlug("outColor").connectedTo(colorSrcPlugs,true,false);
			else
				shader.findPlug("color").connectedTo(colorSrcPlugs,true,false);
			for (int i=0; i<colorSrcPlugs.length(); i++)
			{
				if (colorSrcPlugs[i].node().hasFn(MFn::kLayeredTexture))
				{
					pLayeredTexNode = new MFnDependencyNode(colorSrcPlugs[i].node());
					continue;
				}
			}

			// Get inputs to layered texture
			MPlug inputsPlug = pLayeredTexNode->findPlug("inputs");

			// Scan inputs and export textures
			for (int i=inputsPlug.numElements()-1; i>=0; i--)
			{
				MFnDependencyNode* pTextureNode = NULL;
				// Search for a connected texture
				inputsPlug[i].child(0).connectedTo(colorSrcPlugs,true,false);
				for (int j=0; j<colorSrcPlugs.length(); j++)
				{
					if (colorSrcPlugs[j].node().hasFn(MFn::kFileTexture))
					{
						pTextureNode = new MFnDependencyNode(colorSrcPlugs[j].node());
						continue;
					}
				}

				// Translate the texture if it was found
				if (pTextureNode)
				{
					// Get blend mode
					TexOpType opType;
					short bm;
					inputsPlug[i].child(2).getValue(bm);
					switch(bm)
					{				
					case 0:
						opType = TOT_REPLACE;
						break;
					case 1:
						opType = TOT_ALPHABLEND;
						break;				
					case 4:
						opType = TOT_ADD;
						break;
					case 6:
						opType = TOT_MODULATE;
						break;
					default:
						opType = TOT_MODULATE;
					}

					stat = loadTexture(pTextureNode,opType,uvsets,params);
					delete pTextureNode;
					if (MS::kSuccess != stat)
					{
						std::cout << "Error loading layered texture\n";
						std::cout.flush();
						delete pLayeredTexNode;
						return MS::kFailure;
					}
				}
			}
			if (pLayeredTexNode)
				delete pLayeredTexNode;
		}
		// Else translate the single texture
		else
		{
			// Get texture node
			MFnDependencyNode* pTextureNode = NULL;
			if (m_type == MT_SURFACE_SHADER)
				shader.findPlug("outColor").connectedTo(colorSrcPlugs,true,false);
			else
				shader.findPlug("color").connectedTo(colorSrcPlugs,true,false);
			for (int i=0; i<colorSrcPlugs.length(); i++)
			{
				if (colorSrcPlugs[i].node().hasFn(MFn::kFileTexture))
				{
					pTextureNode = new MFnDependencyNode(colorSrcPlugs[i].node());
					continue;
				}
			}
			if (pTextureNode)
			{
				TexOpType opType = TOT_MODULATE;
				stat = loadTexture(pTextureNode,opType,uvsets,params);
				delete pTextureNode;
				if (MS::kSuccess != stat)
				{
					std::cout << "Error loading texture\n";
					std::cout.flush();
					return MS::kFailure;
				}
			}
		}
	}

#endif //DO_TEXTURES

  mesh.getUVSetNames(m_uvsets);

  set = m_uvsets[0];
  MObjectArray textures;
  mesh.getAssociatedUVSetTextures(set, textures);
  if(textures.length() == 0)
    return MS::kSuccess;
  
  m_textureObjs.push_back(textures);
  m_image = new MImage;
  MStatus stat_read = m_image->readFromTextureNode(textures[0]);
  if(!stat_read){
    // error reading texture node
    cout << "Material::load(): error reading texture node." << std::endl;
    release_image();
    return MS::kFailure;
  }

  // read worked
  m_isTextured = true;
	return MS::kSuccess;
}


// Load a surface shader
MStatus Material::loadSurfaceShader(MObject &shaderObj)
{
  MFnDependencyNode shader(shaderObj);
	m_type = MT_SURFACE_SHADER;
	MPlugArray colorSrcPlugs;
	// Check if material is textured
	shader.findPlug("outColor").connectedTo(colorSrcPlugs,true,false);
	for (unsigned int i=0; i<colorSrcPlugs.length(); i++)
	{
		if (colorSrcPlugs[i].node().hasFn(MFn::kFileTexture))
		{
			m_isTextured = true;
			continue;
		}
		else if (colorSrcPlugs[i].node().hasFn(MFn::kLayeredTexture))
		{
			m_isTextured = true;
			m_isMultiTextured = true;
			continue;
		}
	}

	// Check if material is transparent
	if (shader.findPlug("outTransparency").isConnected()){
	  shader.findPlug("outTransparencyR").getValue(m_transparency.r);
    shader.findPlug("outTransparencyG").getValue(m_transparency.g);
    shader.findPlug("outTransparencyB").getValue(m_transparency.b);
  }
  
  if(shader.findPlug("diffuse").isConnected()){
    shader.findPlug("diffuse").getValue(m_diffuseCoeff);
  }
  if(shader.findPlug("reflectivity").isConnected()){
    shader.findPlug("reflectivity").getValue(m_reflectivity);
  }
  
	// Get material colours
	if (m_isTextured)
		m_diffuse = MColor(1.0,1.0,1.0,1.0);
	else
	{
		shader.findPlug("outColorR").getValue(m_diffuse.r);
		shader.findPlug("outColorG").getValue(m_diffuse.g);
		shader.findPlug("outColorB").getValue(m_diffuse.b);
		m_diffuse.a = 1.0f - m_transparency.r;
	}
	m_ambient = MColor(0,0,0,1);
	m_emissive = MColor(0,0,0,1);
	m_specular = MColor(0,0,0,1);
	return MS::kSuccess;
}

// Load a lambert shader
MStatus Material::loadLambert(MObject& shaderObj)
{
  MFnDependencyNode shader(shaderObj);
	MPlugArray colorSrcPlugs;
	m_type = MT_LAMBERT;
	MFnLambertShader* pLambert = new MFnLambertShader(shader.object());
	// Check if material is textured
	pLambert->findPlug("color").connectedTo(colorSrcPlugs,true,false);
	for (unsigned int i=0; i<colorSrcPlugs.length(); i++)
	{
		if (colorSrcPlugs[i].node().hasFn(MFn::kFileTexture))
		{
			m_isTextured = true;
			continue;
		}
		else if (colorSrcPlugs[i].node().hasFn(MFn::kLayeredTexture))
		{
			m_isTextured = true;
			m_isMultiTextured = true;
			continue;
		}
	}

	// Check if material is transparent
	//if (pLambert->findPlug("transparency").isConnected()){
    //pLambert->transparency().r>0.0f
		m_transparency = pLambert->transparency();
  //}
  //if(shader.findPlug("diffuse").isConnected()){
    m_diffuseCoeff = pLambert->diffuseCoeff();
  //}
  if(shader.findPlug("reflectivity").isConnected()){
    shader.findPlug("reflectivity").getValue(m_reflectivity);
  }
	// Get material colours
	//diffuse colour
	if (m_isTextured)
		m_diffuse = MColor(1.0,1.0,1.0,1.0);
	else
	{
		m_diffuse = pLambert->color();
		m_diffuse.a = 1.0f - m_transparency.r;
	}
	//ambient colour
	m_ambient = pLambert->ambientColor();
	//emissive colour
	m_emissive = pLambert->incandescence();
	//specular colour
	m_specular = MColor(0,0,0,1);
  m_refractiveIndex = pLambert->refractiveIndex();
	delete pLambert;
	return MS::kSuccess;
}

// Load a phong shader
MStatus Material::loadPhong(MObject& shaderObj)
{
  MFnDependencyNode shader(shaderObj);
	MPlugArray colorSrcPlugs;
	m_type = MT_PHONG;
	MFnPhongShader* pPhong = new MFnPhongShader(shader.object());

	// Check if material is textured
	pPhong->findPlug("color").connectedTo(colorSrcPlugs,true,false);
	for (unsigned int i=0; i<colorSrcPlugs.length(); i++)
	{
		if (colorSrcPlugs[i].node().hasFn(MFn::kFileTexture))
		{
			m_isTextured = true;
			continue;
		}
		else if (colorSrcPlugs[i].node().hasFn(MFn::kLayeredTexture))
		{
			m_isTextured = true;
			m_isMultiTextured = true;
			continue;
		}
	}

	// Check if material is transparent
	//if (pPhong->findPlug("transparency").isConnected()){
		m_transparency = pPhong->transparency();
  //}
  //if(shader.findPlug("diffuse").isConnected()){
    m_diffuseCoeff = pPhong->diffuseCoeff();
  //}
  //if(shader.findPlug("reflectivity").isConnected()){
    m_reflectivity = pPhong->reflectivity();
  //}

	// Get material colours
	//diffuse colour
	if (m_isTextured)
		m_diffuse = MColor(1.0,1.0,1.0,1.0);
	else
	{
		m_diffuse = pPhong->color();
		m_diffuse.a = 1.0f - m_transparency.r;
	}
	//ambient colour
	m_ambient = pPhong->ambientColor();
	//emissive colour
	m_emissive = pPhong->incandescence();
	//specular colour
	m_specular = pPhong->specularColor();
	m_specular.a = pPhong->cosPower()*1.28f;
  // refractive index
  m_refractiveIndex = pPhong->refractiveIndex();
	delete pPhong;
	return MS::kSuccess;
}


// load a blinn shader
MStatus Material::loadBlinn(MObject& shaderObj)
{
  MFnDependencyNode shader(shaderObj);
	MPlugArray colorSrcPlugs;
	m_type = MT_BLINN;
	MFnBlinnShader* pBlinn = new MFnBlinnShader(shader.object());
	// Check if material is textured
	pBlinn->findPlug("color").connectedTo(colorSrcPlugs,true,false);
	for (unsigned int i=0; i<colorSrcPlugs.length(); i++)
	{
		if (colorSrcPlugs[i].node().hasFn(MFn::kFileTexture))
		{
			m_isTextured = true;
			continue;
		}
		else if (colorSrcPlugs[i].node().hasFn(MFn::kLayeredTexture))
		{
			m_isTextured = true;
			m_isMultiTextured = true;
			continue;
		}
	}

	// Check if material is transparent
	//if (pBlinn->findPlug("transparency").isConnected()){
    m_transparency = pBlinn->transparency();
  //}
  //if(shader.findPlug("diffuse").isConnected()){
    m_diffuseCoeff = pBlinn->diffuseCoeff();
  //}
  //if(shader.findPlug("reflectivity").isConnected()){
    m_reflectivity = pBlinn->reflectivity();
  //}
	// Get material colours
	//diffuse colour
	if (m_isTextured)
		m_diffuse = MColor(1.0,1.0,1.0,1.0);
	else
	{
		m_diffuse = pBlinn->color();
		m_diffuse.a = 1.0f - m_transparency.r;
	}
	//ambient colour
	m_ambient = pBlinn->ambientColor();
	//emissive colour
	m_emissive = pBlinn->incandescence();
	//specular colour
	m_specular = pBlinn->specularColor();
	m_specular.a = 128.0f - (128.0f*pBlinn->eccentricity());
  m_refractiveIndex = pBlinn->refractiveIndex();
	delete pBlinn;
	return MS::kSuccess;
}

// load a cgFx shader
//MStatus Material::loadCgFxShader(MFnDependencyNode *pShader)
//{
//	m_type = MT_CGFX;
//	// Create a default white lambert
//	m_isTextured = false;
//	m_isMultiTextured = false;
//	m_diffuse = MColor(1.0,1.0,1.0,1.0);
//	m_specular = MColor(0,0,0,1);
//	m_emissive = MColor(0,0,0,1);
//	m_ambient = MColor(0,0,0,1);
//	return MS::kSuccess;
//}

// Load texture data from a texture node
MStatus Material::loadTexture(MFnDependencyNode* pTexNode,TexOpType& opType,MStringArray& uvsets/*,ParamList& params*/)
{
	Texture tex;
	// Get texture filename
	MString filename, absFilename;
	MRenderUtil::exactFileTextureName(pTexNode->object(),absFilename);
	filename = absFilename.substring(absFilename.rindex('/')+1,absFilename.length()-1);
	MString command = "toNativePath(\"";
	command += absFilename;
	command += "\")";
	MGlobal::executeCommand(command,absFilename);
	tex.absFilename = absFilename;
	tex.filename = filename;
	tex.uvsetIndex = 0;
	tex.uvsetName = "";
	// Set texture operation type
	tex.opType = opType;
	// Get connections to uvCoord attribute of texture node
	MPlugArray texSrcPlugs;
	pTexNode->findPlug("uvCoord").connectedTo(texSrcPlugs,true,false);
	// Get place2dtexture node (if connected)
	MFnDependencyNode* pPlace2dTexNode = NULL;
	for (unsigned int j=0; j<texSrcPlugs.length(); j++)
	{
		if (texSrcPlugs[j].node().hasFn(MFn::kPlace2dTexture))
		{
			pPlace2dTexNode = new MFnDependencyNode(texSrcPlugs[j].node());
			continue;
		}
	}
	// Get uvChooser node (if connected)
	MFnDependencyNode* pUvChooserNode = NULL;
	if (pPlace2dTexNode)
	{
		MPlugArray placetexSrcPlugs;
		pPlace2dTexNode->findPlug("uvCoord").connectedTo(placetexSrcPlugs,true,false);
		for (unsigned int j=0; j<placetexSrcPlugs.length(); j++)
		{
			if (placetexSrcPlugs[j].node().hasFn(MFn::kUvChooser))
			{
				pUvChooserNode = new MFnDependencyNode(placetexSrcPlugs[j].node());
				continue;
			}
		}
	}
	// Get uvset index
	if (pUvChooserNode)
	{
		bool foundMesh = false;
		bool foundUvset = false;
		MPlug uvsetsPlug = pUvChooserNode->findPlug("uvSets");
		MPlugArray uvsetsSrcPlugs;
		for (unsigned int i=0; i<uvsetsPlug.evaluateNumElements() && !foundMesh; i++)
		{
			uvsetsPlug[i].connectedTo(uvsetsSrcPlugs,true,false);
			for (unsigned int j=0; j<uvsetsSrcPlugs.length() && !foundMesh; j++)
			{
				if (uvsetsSrcPlugs[j].node().hasFn(MFn::kMesh))
				{
					uvsetsSrcPlugs[j].getValue(tex.uvsetName);
					for (unsigned int k=0; k<uvsets.length() && !foundUvset; k++)
					{
						if (uvsets[k] == tex.uvsetName)
						{
							tex.uvsetIndex = k;
							foundUvset = true;
						}
					}
				}
			}
		}
	}
	// Get texture options from Place2dTexture node
	if (pPlace2dTexNode)
	{
		// Get address mode
		//U
		bool wrapU, mirrorU;
		pPlace2dTexNode->findPlug("wrapU").getValue(wrapU);
		pPlace2dTexNode->findPlug("mirrorU").getValue(mirrorU);
		if (mirrorU)
			tex.am_u = TAM_MIRROR;
		else if (wrapU)
			tex.am_u = TAM_WRAP;
		else
			tex.am_u = TAM_CLAMP;
		// V
		bool wrapV,mirrorV;
		pPlace2dTexNode->findPlug("wrapV").getValue(wrapV);
		pPlace2dTexNode->findPlug("mirrorV").getValue(mirrorV);
		if (mirrorV)
			tex.am_v = TAM_MIRROR;
		else if (wrapV)
			tex.am_v = TAM_WRAP;
		else
			tex.am_v = TAM_CLAMP;
		// Get texture scale
		double covU,covV;
		pPlace2dTexNode->findPlug("coverageU").getValue(covU);
		pPlace2dTexNode->findPlug("coverageV").getValue(covV);
		tex.scale_u = covU;
		if (fabs(tex.scale_u) < PRECISION)
			tex.scale_u = 0;
		tex.scale_v = covV;
		if (fabs(tex.scale_v) < PRECISION)
			tex.scale_v = 0;
		// Get texture scroll
		double transU,transV;
		pPlace2dTexNode->findPlug("translateFrameU").getValue(transU);
		pPlace2dTexNode->findPlug("translateFrameV").getValue(transV);
		tex.scroll_u = -0.5 * (covU-1.0)/covU - transU/covU;
		if (fabs(tex.scroll_u) < PRECISION)
			tex.scroll_u = 0;
		tex.scroll_v = 0.5 * (covV-1.0)/covV + transV/covV;
		if (fabs(tex.scroll_v) < PRECISION)
			tex.scroll_v = 0;
		// Get texture rotation
		double rot;
		pPlace2dTexNode->findPlug("rotateFrame").getValue(rot);
		tex.rot = -rot;
		if (fabs(rot) < PRECISION)
			tex.rot = 0;
	}
	// add texture to material texture list
	m_textures.push_back(tex);
	// free up memory
	if (pUvChooserNode)
		delete pUvChooserNode;
	if (pPlace2dTexNode)
		delete pPlace2dTexNode;

	return MS::kSuccess;
}

void Material::getBilinearFilteredPixelColor(float u, float v, MColor& color) const 
{
  if(!m_image){
    color = MColor(0,0,0);
    return;
  }
  unsigned int sizex, sizey;
  m_image->getSize(sizex,sizey);
  float u_scaled = u * (sizex-1);
  float v_scaled = v * (sizey-1);
  int u_left = (int)floor(u_scaled);
  int v_up = (int)floor(v_scaled);
  float u_ratio = u_scaled - u_left;
  float v_ratio = v_scaled - v_up;
  float u_opposite = 1 - u_ratio;
  float v_opposite = 1 - v_ratio;
  unsigned char* pixel_array = m_image->pixels();
  int depth = m_image->depth();

  unsigned char tl = pixel_array[v_up*sizex*depth + u_left*depth];
  unsigned char tr = pixel_array[v_up*sizex*depth + (u_left+1)*depth];
  unsigned char bl = pixel_array[(v_up+1)*sizex*depth + u_left*depth];
  unsigned char br = pixel_array[(v_up+1)*sizex*depth + (u_left+1)*depth];
  color.r = (tl*u_opposite + tr*u_ratio)*v_opposite + (bl*u_opposite + br*u_ratio)*v_ratio;

  tl = pixel_array[v_up*sizex*depth + u_left*depth + 1];
  tr = pixel_array[v_up*sizex*depth + (u_left+1)*depth + 1];
  bl = pixel_array[(v_up+1)*sizex*depth + u_left*depth + 1];
  br = pixel_array[(v_up+1)*sizex*depth + (u_left+1)*depth + 1];
  color.g = (tl*u_opposite + tr*u_ratio)*v_opposite + (bl*u_opposite + br*u_ratio)*v_ratio;

  tl = pixel_array[v_up*sizex*depth + u_left*depth + 2];
  tr = pixel_array[v_up*sizex*depth + (u_left+1)*depth + 2];
  bl = pixel_array[(v_up+1)*sizex*depth + u_left*depth + 2];
  br = pixel_array[(v_up+1)*sizex*depth + (u_left+1)*depth + 2];
  color.b = (tl*u_opposite + tr*u_ratio)*v_opposite + (bl*u_opposite + br*u_ratio)*v_ratio;

  tl = pixel_array[v_up*sizex*depth + u_left*depth + 3];
  tr = pixel_array[v_up*sizex*depth + (u_left+1)*depth + 3];
  bl = pixel_array[(v_up+1)*sizex*depth + u_left*depth + 3];
  br = pixel_array[(v_up+1)*sizex*depth + (u_left+1)*depth + 3];
  color.a  = (tl*u_opposite + tr*u_ratio)*v_opposite + (bl*u_opposite + br*u_ratio)*v_ratio;

  color /= 255.0f;
 }
